import schedule from './schedule'
import { forEach, isArray, isPlainObject } from './utils'

const proto = Array.prototype
const newArrayProto = Object.create(proto)
;['push', 'unshift', 'splice', 'sort', 'reverse', 'pop', 'shift'].forEach(
  method => {
    const oldMethod = newArrayProto[method]
    newArrayProto[method] = function (...args) {
      // 触发本身
      const res = oldMethod.apply(this, args)
      let arr = []
      switch (method) {
        case 'push':
        case 'unshift':
          arr = args
          break
        case 'splice':
          arr = args.slice(2)
          break
      }
      // 响应式
      observe(arr)
      // 触发依赖
      this.__ob__.dep.notify()
      return res
    }
  }
)
export function observe(target) {
  if (!isPlainObject(target) && !isArray(target)) return
  if (target.__ob__) return
  Object.defineProperty(target, '__ob__', {
    value: {},
    enumerable: false
  })
  if (isPlainObject(target)) {
    forEach(target, (value, key) => {
      defineReactive(target, key, value)
    })
  } else if (isArray(target)) {
    target.__proto__ = newArrayProto
    const dep = new Dep()
    target.__ob__.dep = dep
    target.forEach(o => observe(o))
  }
}
export function defineReactive(target, key, value) {
  observe(value)
  const dep = new Dep()
  target.__ob__.dep = dep
  Object.defineProperty(target, key, {
    get() {
      // 收集依赖
      dep.append()
      if (isArray(value)) {
        // 如果属性是数组，让数组也收集依赖
        value.__ob__.dep.append()
      }
      return value
    },
    set(val) {
      value = val
      // 响应式
      observe(value)
      // 触发更新
      dep.notify()
      observe(value)
    },
    enumerable: true,
    configurable: false
  })
}

export class Dep {
  constructor() {
    this.watchers = []
  }
  static target = null
  append() {
    if (Dep.target && !this.watchers.includes(Dep.target)) {
      this.watchers.push(Dep.target)
    }
  }
  notify() {
    this.watchers.forEach(watcher => {
      watcher.run()
    })
  }
}
export class Watcher {
  constructor(express, callback, options) {
    this.dirty = true
    this.value = null
    this.express = express
    this.callback = callback || express
    this.options = options || {}
    this.getter()
  }
  getter() {
    // if (isFunction(this.express)) {
    //   return this.express()
    // }
    Dep.target = this
    this.express()
    if (this.options.immediate) {
      this.run()
    }
  }
  getValue() {
    if (!this.dirty) return this.value
    this.dirty = false
    return this.callback()
  }
  run = () => {
    schedule.enqueue(this)
  }
  update = () => {
    this.dirty = true
    this.callback()
  }
}
export class Event {
  constructor() {
    this.event = new Map()
  }
  init(merOps) {
    forEach(merOps.listener, (fn, key) => {
      this.event.set(key, fn)
    })
  }
  add(name, fn) {
    this.event.set(name, fn)
  }
  trigger(name) {
    this.event.get(name)?.()
  }
}
export class LifeCircle {
  constructor() {
    this.lifeCircle = new Map()
    this.lifeCircle.set('beforeCreate', [])
    this.lifeCircle.set('created', [])
    this.lifeCircle.set('beforeMount', [])
    this.lifeCircle.set('mounted', [])
    this.lifeCircle.set('beforeDestroy', [])
    this.lifeCircle.set('destroyed', [])
    this.lifeCircle.set('activated', [])
    this.lifeCircle.set('deactivated', [])
    this.lifeCircle.set('errorCapcherd', [])
  }

  init(options) {
    forEach(options, (val, key) => {
      if (this.lifeCircle.has(key)) {
        this.lifeCircle.get(key).push(val)
      }
    })
  }

  push(name, fn) {
    this.lifeCircle.get(name).push(fn)
  }

  trigger(name) {
    this.lifeCircle.get(name).forEach(fn => fn())
  }
}
